using System.Collections.Generic;
using System.Reflection;

namespace NUtil.beans {
    public class BeanCopier {
        private object[] EMPTY_OBJECT_ARRAY = new object[0];

        public void CopyPropertyBased(object destination, object source) {
            if (destination != null && source != null) {
                IList<PropertyInfo> destinationProperties = destination.GetType().GetProperties();
                CopyProperties(destination, source, destinationProperties);
            }
        }

        public void CopyPropertyBased(object destination, object source, string [] propertiesToInclude) {
            if (destination != null && source != null) {
                IList<PropertyInfo> destinationProperties = BeanIntrospector.GetPropertiesInclude(destination, propertiesToInclude);
                CopyProperties(destination, source, destinationProperties);
            }
        }

        public void CopyPropertyBasedExclude(object destination, object source, string[] propertiesToExclude)
        {
            if (destination != null && source != null)
            {
                IList<PropertyInfo> destinationProperties = BeanIntrospector.GetPropertiesExclude(destination, propertiesToExclude);
                CopyProperties(destination, source, destinationProperties);
            }
        }

        private void CopyProperties(object destination, object source, IList<PropertyInfo> destinationProperties)
        {
            foreach (PropertyInfo destinationProperty in destinationProperties) {
                PropertyInfo matchingSourceProperty = source.GetType().GetProperty(destinationProperty.Name);
                destinationProperty.SetValue(destination, matchingSourceProperty.GetValue(source, EMPTY_OBJECT_ARRAY), EMPTY_OBJECT_ARRAY);
            }
        }

        public void CopyFieldBased(object destination, object source, string[] fieldsToInclude) {
            if (destination != null && source != null)
            {
                IList<FieldInfo> fields = new List<FieldInfo>();
                foreach (string fieldName in fieldsToInclude) {
                    FieldInfo field = destination.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
                    if (field != null) {
                        fields.Add(field);
                    }
                }
                copyAllFields(destination, fields, source);
            }
        }
        
        public void CopyFieldBased(object destination, object source) {
            if (destination != null && source != null) {
                IList<FieldInfo> destinationProperties = new List<FieldInfo>(destination.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
                copyAllFields(destination, destinationProperties, source);
            }
        }

        private static void copyAllFields(object destination, IList<FieldInfo> destinationProperties, object source) {
            foreach (FieldInfo desinationField in destinationProperties) {
                FieldInfo matchingSourceField = source.GetType().GetField(desinationField.Name, BindingFlags.NonPublic | BindingFlags.Instance);
                desinationField.SetValue(destination, matchingSourceField.GetValue(source));
            }
        }

        public void CopyFieldBasedExclude(object destination, object source, string[] fieldsToExclude) {
            if (destination != null && source != null)
            {
                IList<FieldInfo> fields = new List<FieldInfo>(destination.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
                foreach (string fieldName in fieldsToExclude)
                {
                    FieldInfo field = destination.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
                    if (field != null)
                    {
                        fields.Remove(field);
                    }
                }
                copyAllFields(destination, fields, source);
            }
        }

    }
}